package org.ocean.general.sample.helper;

import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.scripting.xmltags.SqlNode;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.ocean.general.sample.exception.BatisException;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public abstract class MapperTemplate {
    private static final String DYNAMIC_SQL="dynamicSql";
    private static final XMLLanguageDriver languageDriver = new XMLLanguageDriver();
    protected Map<String, Method> methodMap = new ConcurrentHashMap<>();
    protected Map<String,Class<?>> entityClassMap = new ConcurrentHashMap<>();
    protected Class<?> mapperClass;
    protected MapperHelper mapperHelper;

    public MapperTemplate(Class<?> mapperClass,MapperHelper mapperHelper){
        this.mapperClass = mapperClass;
        this.mapperHelper = mapperHelper;
    }

    public Class<?> getEntityClass(MappedStatement mappedStatement){
        Class<?> mapperClass = BatisHelper.getMapperClass(mappedStatement.getId());
        Type[] types = mapperClass.getGenericInterfaces();
        for (Type type:types){
            if(type instanceof ParameterizedType){
                ParameterizedType parameterizedType = (ParameterizedType) type;
                if(parameterizedType.getRawType() == this.mapperClass || this.mapperClass.isAssignableFrom((Class<?>) parameterizedType.getRawType())){
                    Class<?> retrunType  = (Class<?>) parameterizedType.getActualTypeArguments()[0];
                    return retrunType;
                }
            }
        }
        throw new BatisException("无法获取 " + mappedStatement.getId() + " 方法的泛型信息!");
    }

    public void addMethod(String methodName,Method method){
        methodMap.put(methodName,method);
    }

    public boolean supportMethod(String msId){
        Class<?> mapperClass = BatisHelper.getMapperClass(msId);
        if(Objects.nonNull(mapperClass) && this.mapperClass.isAssignableFrom(mapperClass)){
            String methodName = BatisHelper.getMethodName(msId);
            return Objects.nonNull(methodMap.get(methodName));
        }
        return false;
    }

    /**
     * 封装通用返回对象字段列
     * @param mappedStatement
     * @param entityClass
     */
    protected void setResultType(MappedStatement mappedStatement,Class<?> entityClass){
        List<ResultMap> list = new ArrayList<>();
        list.add(OptionalProviderHelper.getResultMap(mappedStatement.getConfiguration(), entityClass));
        MetaObject metaObject = SystemMetaObject.forObject(mappedStatement);
        metaObject.setValue("resultMaps", Collections.unmodifiableList(list));
    }

    protected void setJoinResultType(MappedStatement mappedStatement,Class<?> entityClass){
        List<ResultMap> list = new ArrayList<>();
        list.add(OptionalProviderHelper.getResultMap(mappedStatement.getConfiguration(), entityClass));
        MetaObject metaObject = SystemMetaObject.forObject(mappedStatement);
        metaObject.setValue("resultMaps", Collections.unmodifiableList(list));
    }


    public void setSqlSource(MappedStatement mappedStatement){
        if(this.mapperClass == BatisHelper.getMapperClass(mappedStatement.getId())){
            throw  new BatisException("请不要配置或扫描通用Mapper接口类");
        }
        Method method = methodMap.get(BatisHelper.getMethodName(mappedStatement.getId()));
        try {
            if(method.getReturnType() == Void.TYPE){
                method.invoke(this,mappedStatement);
            }else if (SqlNode.class.isAssignableFrom(method.getReturnType())){
                SqlNode sqlNode = (SqlNode) method.invoke(this,mappedStatement);
                DynamicSqlSource dynamicSqlSource =new DynamicSqlSource(mappedStatement.getConfiguration(),sqlNode);
                setSqlSource(mappedStatement,dynamicSqlSource);
            }else if(String.class.equals(method.getReturnType())){
                String xmlSql = (String) method.invoke(this,mappedStatement);
                SqlSource sqlSource = createSqlSource(mappedStatement,xmlSql);
                setSqlSource(mappedStatement,sqlSource);
            }else{
                throw  new BatisException("自定义Mapper方法返回类型错误，只可返回Void，SqlNode,String三种类型");
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
    public SqlSource createSqlSource(MappedStatement mappedStatement,String xmlSql){
        return languageDriver.createSqlSource(mappedStatement.getConfiguration(),"<script>\n\t"+xmlSql+"</script>",null);
    }

    protected void setSqlSource(MappedStatement mappedStatement, SqlSource sqlSource){

        MetaObject metaObject = BatisHelper.forObject(mappedStatement);
        metaObject.setValue("sqlSource",sqlSource);

    }

}
